from __future__ import annotations

"""
通用工具函数
------------
职责：
- 口令生成/哈希、UTC 时间、金额计算、CSV 导出等

注意：
- `generate_token` 仅用于一次性凭据/ID 生成，不用于密码
- `calculate_notional` 支持固定名义金额与按权益比例两种模式
"""

import csv
import datetime as dt
import hashlib
import io
import secrets
import string
import re
from typing import Any, Callable, Dict, Iterable, Optional
from zoneinfo import ZoneInfo

from ..config import get_settings

BEIJING_TZ = ZoneInfo("Asia/Shanghai")


def generate_token(length: int = 32) -> str:
    """生成指定长度的字母数字随机串。"""
    alphabet = string.ascii_letters + string.digits
    return "".join(secrets.choice(alphabet) for _ in range(length))


def hash_token(value: str) -> str:
    """对口令/令牌进行 SHA-256 哈希（不可逆）。"""
    return hashlib.sha256(value.encode("utf-8")).hexdigest()


def now_utc() -> dt.datetime:
    """返回当前北京时间（忽略毫秒）。"""
    return dt.datetime.now(tz=BEIJING_TZ).replace(microsecond=0)


def isoformat(dt_value: Optional[dt.datetime]) -> Optional[str]:
    """将时间转换为 ISO8601 字符串（统一为 UTC）。"""
    if dt_value is None:
        return None
    local_dt = dt_value.astimezone(BEIJING_TZ).replace(microsecond=0)
    return local_dt.isoformat()


def calculate_notional(allocation: Dict[str, Any], equity: Optional[float] = None) -> float:
    """根据资金配置计算下单名义金额。

    - fixed_quote：固定名义金额，直接返回 value
    - ratio_of_equity：按权益比例，需要提供 equity
    """
    mode = allocation.get("mode")
    value = allocation.get("value", 0)
    if mode == "fixed_quote":
        return float(value)
    if mode == "ratio_of_equity":
        if equity is None:
            raise ValueError("Equity required for ratio_of_equity mode")
        return float(equity) * float(value)
    raise ValueError("Unsupported allocation mode")


def export_csv(rows: Iterable[Dict[str, Any]]) -> str:
    """将字典列表导出为 CSV 字符串。"""
    buffer = io.StringIO()
    csv_writer = None
    for row in rows:
        if csv_writer is None:
            csv_writer = csv.DictWriter(buffer, fieldnames=list(row.keys()))
            csv_writer.writeheader()
        csv_writer.writerow(row)
    return buffer.getvalue()


RISK_ERROR_MESSAGES: Dict[str, str] = {
    "ERR_CONNECTOR": "交易所未在白名单，已拒绝下单。",
    "ERR_SYMBOL": "交易对未在白名单，已拒绝下单。",
    "ERR_ORDER_TYPE": "订单类型不被允许，请检查配置。",
    "ERR_MIN_NOTIONAL": "名义金额低于交易所最小限制，请提高投入金额或调整价格。",
    "ERR_MAX_NOTIONAL": "名义金额超过风控上限，请降低投入金额。",
    "ERR_AMOUNT": "计算得到的下单数量无效，请调整金额或价格。",
    "ERR_PRICE_REQUIRED": "限价单未提供价格，请补充价格后重试。",
    "ERR_MARKET_PRICE": "缺少有效市价参考，暂时无法下单。",
    "ERR_SLIPPAGE": "滑点设置导致成交价格无效，请调整滑点参数。",
    "ERR_ALLOCATION": "未找到有效的投入比例配置，请检查信号或订阅设置。",
    "ERR_EQUITY": "未能获取账户权益，无法计算下单金额。",
    "ERR_MARKET_DATA": "暂时无法获取盘口行情，请稍后再试。",
}


CUSTOM_ERROR_MESSAGES: Dict[str, str] = {
    "MEMBER_CONNECTOR_MISMATCH": "账户绑定的交易所与广播请求不一致。",
    "CONNECTOR_LOOKUP_ERROR": "查询账户绑定的交易所失败，请稍后再试。",
    "MISSING_CONNECTOR_BINDING": "账户未绑定目标交易所，已拒绝本次下单。",
    "SKIPPED_MISSING_CONNECTOR": "账户未绑定目标交易所，本次已跳过。",
    "EQUITY_FETCH_FAILED": "未能获取账户资产信息，请稍后重试。",
    "INSUFFICIENT_EQUITY": "账户可用余额不足，无法完成下单。",
    "INSUFFICIENT_POSITION": "账户持仓不足，无法完成卖单。",
    "PRICE_UNAVAILABLE": "缺少有效价格，暂时无法计算下单金额。",
    "SLIPPAGE_INVALID": "滑点设置异常，请调整后重试。",
    "MISSING_CONNECTOR": "未配置交易所连接器，请先完成绑定。",
    "CONNECTOR_MISMATCH": "交易所类型与策略不匹配，请检查订阅设置。",
    "RATE_LIMITED": "请求过于频繁，请稍后再试。",
    "ALL_FAILED": "所有账户下单均失败，请检查配置或余额后再试。",
    "HB_UNREACHABLE": "撮合服务暂不可用，请稍后重试。",
    "CONNECTOR_LOOKUP_FAILED": "查询账户绑定的交易所失败，请稍后重试。",
    "SCRIPT_MISSING": "策略不存在或已删除，信号已被拒绝。",
    "SCRIPT_PAUSED": "策略已暂停，信号未被执行。",
    "NO_SUBSCRIBERS": "当前没有有效订阅者，信号已被忽略。",
}


def _build_min_notional_message(raw: str) -> str:
    required = None
    symbol = None
    match_required = re.search(r"minimum notional size\s*([0-9.]+)", raw, re.IGNORECASE)
    if match_required:
        required = match_required.group(1)
    match_symbol = re.search(r"for\s+([A-Z0-9._:/-]+)", raw)
    if match_symbol:
        symbol = match_symbol.group(1)
    message = "名义金额不足"
    details = []
    if symbol:
        details.append(f"交易对 {symbol}")
    if required:
        details.append(f"交易所至少需要 {required}")
    if details:
        message += "（" + "，".join(details) + "）"
    message += "，请提高投入金额或调整价格。"
    return message


RAW_ERROR_PATTERNS: Iterable[tuple[Callable[[str], bool], Callable[[str], str]]] = [
    (lambda txt: "minimum notional" in txt, _build_min_notional_message),
    (
        lambda txt: "balance_not_enough" in txt
        or "not enough balance" in txt
        or "insufficient balance" in txt,
        lambda _: "账户余额不足，请检查资产后再试。",
    ),
    (
        lambda txt: "insufficient margin" in txt,
        lambda _: "保证金不足，无法完成下单。",
    ),
    (
        lambda txt: "too many requests" in txt or "rate limit" in txt,
        lambda _: "请求过于频繁，请稍后再试。",
    ),
    (
        lambda txt: "timeout" in txt or "timed out" in txt or "time out" in txt,
        lambda _: "调用撮合服务超时，请稍后再试。",
    ),
    (
        lambda txt: "service unavailable" in txt or "503" in txt,
        lambda _: "撮合服务暂不可用，请稍后重试。",
    ),
]


def translate_error_message(code: Optional[str] = None, raw: Optional[str] = None) -> str:
    """将错误码或原始信息翻译为中文提示，并移除敏感词。"""
    default_message = "下单失败，请稍后再试。"
    if code:
        key = str(code).strip().upper()
        if key in RISK_ERROR_MESSAGES:
            return RISK_ERROR_MESSAGES[key]
        if key in CUSTOM_ERROR_MESSAGES:
            return CUSTOM_ERROR_MESSAGES[key]
    if raw:
        clean = str(raw).replace("hummingbot-api", "").replace("hummingbot api", "")
        clean = re.sub(r"\s+", " ", clean).strip(" ，,:;.")
        lower = clean.lower()
        for matcher, builder in RAW_ERROR_PATTERNS:
            try:
                if matcher(lower):
                    return builder(clean)
            except Exception:
                continue
    return default_message


def default_account_name(user_id: int, email: Optional[str] = None) -> str:
    """生成默认账户名：优先使用邮箱，否则使用前缀+用户ID。"""
    if email:
        return email
    settings = get_settings()
    return f"{settings.default_account_prefix}_{user_id}"
